#include <QRegExp>
#include <QStringList>
#include <QMap>
#include "text.h"
#include "krautchanparser.h"
#include "timestampparser.h"

namespace Wishmaster
{
	class KrautchanParserImpl
	{
	public:
		KrautchanParserImpl(const QString& chanName, const QString& boardname) : m_chanName(chanName), m_boardname(boardname)
		{
			m_postrx = QRegExp("<div class=\"postheader\">.*(?:onmouseover=\"helpTip\\('([^']+)'\\)\".*)?<span class=\"postsubject\">(.*)</span>\\s+<span class=\"postername\">([^<]*)</span>.*<span class=\"postdate\">([^<]*)</span>.*<a href=\"#(\\d+)\" class=\"quotelink\"(.*)<blockquote>(.*)</blockquote>");
			m_postrx.setMinimal(true);
			m_filerx = QRegExp("toggleThumbnail\\(\\d+,\\s+'([^']*)', \\d+, \\d+,\\s+'([^']*)', \\d+, \\d+\\)");
			m_filerx.setMinimal(true);
		}

		PostInfo nextPost(const ThreadId& threadId, const QString& str, int& pos);
		ThreadInfo parseThreadEntry(const QString& chanName, const QString& boardId, const QString& str);


		QString m_chanName;
		QString m_boardname;
		QRegExp m_postrx;
		QRegExp m_filerx;
		TimestampParser m_timestampParser;
	};

	KrautchanParser::KrautchanParser(const QString& chanName, const QString& boardname) : QObject(),
		BoardParser()
	{
		m_impl = new KrautchanParserImpl(chanName, boardname);
	}

	KrautchanParser::~KrautchanParser()
	{
		delete m_impl;
	}

	QList<ThreadInfo> KrautchanParser::getThreadList(const QString& chanName, const QString& boardId, const QByteArray& input)
	{
		QList<ThreadInfo> list;
		QString str = QString::fromUtf8(input);
		QRegExp hr("<\\s*hr[^>]*>");
		QStringList entries = str.split(hr);
		int i = 0;
		foreach(QString l, entries)
		{
			ThreadInfo info = m_impl->parseThreadEntry(chanName, boardId, l);
			if(info.thread_id.isValid())
			{
				info.timestamp = (1 << 30) - i - 1;
				list.append(info);
			}
			i++;
		}
		return list;
	}

	QList<PostInfo> KrautchanParser::getPosts(const ThreadId& threadId, const QByteArray& input)
	{
		QList<PostInfo> list;
		int pos = 0;
		list.clear();
		QString str = QString::fromUtf8(input);
		QMap<PostId, int> positions;
		int i = 0;
		while(1)
		{
			PostInfo info = m_impl->nextPost(threadId, str, pos);
			if(!info.post_id.isValid())
				break;
			positions.insert(info.post_id, i);
			QList<PostId> links = findLinks(threadId, info.text);
			foreach(PostId link, links)
			{
				int p = positions.value(link, -1);
				if(p == -1)
					continue;
				list[p].replies.insert(info.post_id);
			}

			list.append(info);
			i++;
		}
		return list;
	}

	PostId KrautchanParser::getPostID(const QString& link, const ThreadId& threadId)
	{
		QRegExp rx("#(\\d+)");
		if(rx.indexIn(link) == -1)
			return PostId();
		return PostId(threadId.chanName(), threadId.boardId(), threadId.threadNumericId(), rx.cap(1).toUInt());
	}

	QString KrautchanParser::id()
	{
		return "krautchan";
	}

	BoardParser* KrautchanParser::clone(const QString& chanName, const QString& boardname)
	{
		return new KrautchanParser(chanName, boardname);
	}

	QList<PostId> KrautchanParser::findLinks(const ThreadId& threadId, const QString& posttext)
	{
		QList<PostId> links;
		QRegExp rx("onclick=\"highlightPost\\('(\\d+)'\\);\"");
		int pos = 0;
		while((pos = rx.indexIn(posttext, pos)) != -1)
		{
			links.append(PostId(threadId.chanName(), threadId.boardId(), threadId.threadNumericId(), rx.cap(1).toUInt()));
			pos += rx.matchedLength();
		}
		return links;
	}

	PostId KrautchanParser::parseLink(const QString& url)
	{
		QRegExp rx("http://(.*)/(.*)/res/(\\d+)\\.html(?:#(\\d+))?$");
		if(rx.indexIn(url) < 0)
			return PostId();
		QString host = m_impl->m_chanName;
		return PostId(host, rx.cap(2).prepend('/').append('/'), rx.cap(3).toULong(), rx.cap(4).toULong());
	}

	PostInfo KrautchanParserImpl::nextPost(const ThreadId& threadId, const QString& str, int& pos)
	{
		PostInfo info;
		info.post_id = PostId();
		int p = m_postrx.indexIn(str, pos);
		if(p == -1)
			return info;

		pos = p + m_postrx.matchedLength();

		info.name = htmlToPlainText(m_postrx.cap(3));
		info.name = info.name.trimmed();
		info.topic = htmlToPlainText(m_postrx.cap(2));
		info.topic = info.topic.trimmed();
		info.text = m_postrx.cap(7);
		info.timestamp = m_timestampParser.parse(m_postrx.cap(4)).toTime_t();
		info.post_id = PostId(threadId.chanName(), threadId.boardId(), threadId.threadNumericId(), m_postrx.cap(5).toUInt());
		if(!m_postrx.cap(1).isEmpty())
		{
			ExtraInfo extra;
			extra.id = "country";
			extra.saveable = false;
			extra.type = "Poster country";
			extra.description = QString("Country: ") + m_postrx.cap(1);
			info.extras.append(extra);
		}
		QString files = m_postrx.cap(6);
		p = 0;
		while((p = m_filerx.indexIn(files, p)) != -1)
		{
			AttachmentInfo attach;
			attach.thumbUrl = m_filerx.cap(1);
			attach.url = m_filerx.cap(2);
			info.attachments.append(attach);
			p += m_filerx.matchedLength();
		}
		return info;
	}

	ThreadInfo KrautchanParserImpl::parseThreadEntry(const QString& chanName, const QString& boardId, const QString& str)
	{
		ThreadInfo info;
		info.thread_id = ThreadId();
		QString thread_regexp = "\\[<a href=\"%BOARD%thread-(\\d+)\\.html\">[^<]*</a>\\].*<blockquote>(.*)</blockquote>";
		thread_regexp.replace(QString("%BOARD%"), m_boardname);
		QRegExp rx(thread_regexp);
		rx.setMinimal(true);
		int pos = rx.indexIn(str, 0);
		if(pos < 0)
			return info;
		info.thread_id = ThreadId(chanName, boardId, rx.cap(1).toULong());
		info.topic = htmlToPlainText(rx.cap(2)).trimmed();
		if(info.topic.isEmpty())
		{
			info.topic = QString("#") + info.thread_id.threadStringId();
		}
		QRegExp img("<div class=\"file_thread\">.*toggleThumbnail\\(\\d+,\\s+'([^']*)'");
		img.setMinimal(true);
		QRegExp blockquote("<span class=\"postdate\">([^<]*)</span>.*<blockquote>.*</blockquote>");
		blockquote.setMinimal(true);

		int bqs = 0;
		pos = 0;
		int lastpos = pos;
		while((pos = blockquote.indexIn(str, pos)) >= 0)
		{
			bqs++;
			lastpos = pos;
			pos += blockquote.matchedLength();
		}

		blockquote.indexIn(str, lastpos);
		info.timestamp = m_timestampParser.parse(blockquote.cap(1)).toTime_t();

		QRegExp omittedPosts("<span class=\"omittedinfo\">\\D+(\\d+)\\D");
		omittedPosts.setMinimal(true);
		if(omittedPosts.indexIn(str) > 0)
			info.posts = bqs + omittedPosts.cap(1).toInt();
		else
			info.posts = bqs;
		if(img.indexIn(str) > 0)
			info.leaderPicUrl.setPath(img.cap(1));


		return info;
	}

	void KrautchanParser::setTimestampFormat(const QString& format)
	{
		m_impl->m_timestampParser.setPattern(format);
	}
}

Q_EXPORT_PLUGIN2(krautchan, Wishmaster::KrautchanParser);
